/*
 * Copyright 2009 Google Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.gwt.user.client.rpc;

import com.google.gwt.core.client.GWT;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback;

/**
 * This class encapsulates the logic necessary to configure a RequestBuilder for
 * use with an RPC proxy object. Users who wish to alter the specifics of the
 * HTTP requests issued by RPC proxy objects may override the protected
 * <code>doXyz</code> methods and pass an instance of the subclass to
 * {@link ServiceDefTarget#setRpcRequestBuilder}.
 */
public class RpcRequestBuilder {
    /**
     * Used by {@link #doSetContentType}.
     */
    public static final String CONTENT_TYPE_HEADER = "Content-Type";

    /**
     * Used by {@link #doFinish}.
     */
    /*
     * NB: Also used by RpcServlet.
     */
    public static final String MODULE_BASE_HEADER = "X-GWT-Module-Base";

    /**
     * Used by {@link #doFinish}.
     */
    /*
     * NB: Also used by AbstractRemoteServiceServlet.
     */
    public static final String STRONG_NAME_HEADER = "X-GWT-Permutation";

    /**
     * Not exposed directly to the subclass.
     */
    private RequestBuilder builder;

    /**
     * Initialize the RpcRequestBuilder. This method must be called before any of
     * the other methods in this class may be called. Calling <code>create</code>
     * before calling {@link #finish()} will reset the state of the
     * RpcRequestBuilder.
     * <p>
     * This method delegates to {@link #doCreate} to instantiate the
     * RequestBuilder.
     * 
     * @param serviceEntryPoint The URL entry point
     * @return <code>this</code>
     * @see ServiceDefTarget#setServiceEntryPoint(String)
     */
    public final RpcRequestBuilder create(String serviceEntryPoint) {
        builder = doCreate(serviceEntryPoint);
        assert builder != null : "doCreate failed to return a RequestBuilder";
        return this;
    }

    /**
     * This method must be called to return the RequestBuilder that the RPC
     * request will be made with.
     * <p>
     * This method will call {@link #doFinish} before returning the current
     * RequestBuilder.
     */
    public final RequestBuilder finish() {
        try {
            assert builder != null : "Call create() first";
            doFinish(builder);
            return builder;
        } finally {
            builder = null;
        }
    }

    /**
     * Sets the RequestCallback to be used by the RequestBuilder. Delegates to
     * {@link #doSetCallback}.
     * 
     * @param callback the RequestCallback to be used by the RequestBuilder
     * @return <code>this</code>
     */
    public final RpcRequestBuilder setCallback(RequestCallback callback) {
        assert builder != null : "Call create() first";
        doSetCallback(builder, callback);
        return this;
    }

    /**
     * Sets the MIME content type to be used by the RequestBuilder. Delegates to
     * {@link #doSetContentType}.
     * 
     * @param contentType the MIME content type to be used in the request
     * @return <code>this</code>
     */
    public final RpcRequestBuilder setContentType(String contentType) {
        assert builder != null : "Call create() first";
        doSetContentType(builder, contentType);
        return this;
    }

    /**
     * Sets the request data to be sent in the request. Delegates to
     * {@link #doSetRequestData}.
     * 
     * @param data the data to send
     * @return <code>this</code>
     */
    public final RpcRequestBuilder setRequestData(String data) {
        assert builder != null : "Call create() first";
        doSetRequestData(builder, data);
        return this;
    }

    /**
     * Sets the request id of the request. Delegates to {@link #doSetRequestId}.
     * 
     * @param id the issue number of the request
     * @return <code>this</code>
     */
    public final RpcRequestBuilder setRequestId(int id) {
        assert builder != null : "Call create() first";
        doSetRequestId(builder, id);
        return this;
    }

    /**
     * Called by {@link #create} to instantiate the RequestBuilder object.
     * <p>
     * The default implementation creates a <code>POST</code> RequestBuilder with
     * the given entry point.
     * 
     * @param serviceEntryPoint the URL to which the request should be issued
     * @return the RequestBuilder that should be ultimately passed to the
     *         RpcRequestBuilder's caller.
     */
    protected RequestBuilder doCreate(String serviceEntryPoint) {
        return new RequestBuilder(RequestBuilder.POST, serviceEntryPoint);
    }

    /**
     * Called by {@link #finish()} prior to returning the RequestBuilder to the
     * caller.
     * <p>
     * The default implementation sets the {@value #STRONG_NAME_HEADER} header to
     * the value returned by {@link GWT#getPermutationStrongName()}.
     * 
     * @param rb The RequestBuilder that is currently being configured
     */
    protected void doFinish(RequestBuilder rb) {
        rb.setHeader(STRONG_NAME_HEADER, GWT.getPermutationStrongName());
        rb.setHeader(MODULE_BASE_HEADER, GWT.getModuleBaseURL());
    }

    /**
     * Called by {@link #setCallback}.
     * <p>
     * The default implementation calls
     * {@link RequestBuilder#setCallback(RequestCallback)}.
     * 
     * @param rb the RequestBuilder that is currently being configured
     * @param callback the user-provided callback
     */
    protected void doSetCallback(RequestBuilder rb, RequestCallback callback) {
        rb.setCallback(callback);
    }

    /**
     * Called by {@link #setContentType}.
     * <p>
     * The default implementation sets the {@value #CONTENT_TYPE_HEADER} header to
     * the value specified by <code>contentType</code> by calling
     * {@link RequestBuilder#setHeader(String, String)}.
     * 
     * @param rb the RequestBuilder that is currently being configured
     * @param contentType the desired MIME type of the request's contents
     */
    protected void doSetContentType(RequestBuilder rb, String contentType) {
        rb.setHeader(CONTENT_TYPE_HEADER, contentType);
    }

    /**
     * Called by {@link #setRequestData}.
     * <p>
     * The default implementation invokes
     * {@link RequestBuilder#setRequestData(String)}.
     * 
     * @param rb the RequestBuilder that is currently being configured
     * @param data the data to send
     */
    protected void doSetRequestData(RequestBuilder rb, String data) {
        rb.setRequestData(data);
    }

    /**
     * Called by {@link #setRequestId}.
     * <p>
     * The default implementation is a no-op.
     * 
     * @param rb the RequestBuilder that is currently being configured
     * @param id the request's issue id
     */
    protected void doSetRequestId(RequestBuilder rb, int id) {
    }

    public final RpcRequestBuilder setSync(boolean sync) {
        builder.setSync(sync);
        return this;
    }
}
